/*
 * @OrderedQueue.java	2009/04/10
 *
 * Copyright 2008 Web & Zen Multimedia, All rights reserved.
 * Use is subject to license terms.
 */
package it.wzm.lightweightlog;

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Properties;

/**
 * This class store Entries in an ordered queue based on time of insertion. Methods of this class that are dealing with load unload operation are synchronized in that part where they are operating on Queue.
 * It also a guarantee that when a thread is unloading the queue no one can push Entries in it.
 * For doing this we are using the well known wait/notifyAll method plus the setting of a private variable "unload". 
 * @author Leonardo Celati
 * @param <T> T extends Entry
 */
final class OrderedQueue<T extends Entry> extends Queue<T>  {

    /** As required by serialization... */
	private static final long serialVersionUID = -277905281650060923L;
	/** the Entry container for this Queue */
	private final LinkedList<T> store;
	/** tell when we are unloading queue...*/
	private boolean working;
	/** Register */
	private transient final Register<? extends Dumper> r;
	
    /**
     * Default constructor
     * @param p custom set of properties
     */
    public OrderedQueue(Properties p) {
        super(p);
        // entry are stored in a LinkedList to maintain ordering...
        store = new LinkedList<T>();
        r=LogFactory.getRegister();
    }

    /**
     * See if this cell is empty
     * @return true if it's empty, false if not
     */
    public boolean isEmpty() {
        return store.isEmpty();
    }
    
    /**
     * Store the element into the Queue
     * @param T
     * @return true if the element is correctly added to queue
     */
    @Override
    boolean push(T t) throws QueueException {
    	
    	// cannot add null...
    	if (t==null)
    		return false;
    		//throw new QueueException("Cannot push 'null' Entry");
    	
    	// cumulable means we dump immediately...
    	if (!this.isCumulable()) {
            // first we get the register
    		r.dispatchToDumper(t);
    	}
    	
    	// synchronizing only this piece of code where we are
    	// adding element to the queue
    	synchronized(store) {
    		try {
    		
		    	if (this.isWorking()) {
		    		// wait...but not forever...
					store.wait(10000);
		    	}
				// return adding...
			    return store.add(t);
				
    		} catch (InterruptedException e) {
    			return false;
    		} catch (Exception e) {
    			return false;
			}
	    }
    }
    
    /**
     * Get first (lowest) T from Queue...
     */
    @Override
    T pop() throws QueueException {
    	// by contract if cumulable we cannot store anything in queue
    	if (!this.isCumulable()) {
    		return null;
    	}    	
    	
    	// get element from Queue...and remove it
    	// then return...
    	try {
    		synchronized(store) {
				T t = store.get(0);
				store.remove(t);
				return t;
    		}
    	} catch (NoSuchElementException e) {
    		return null;
    	} catch (Exception e) {
    		throw new QueueException("Pop error: " + e.getMessage());
    	}
    }

    /**
     * Retrieve the actual size of this queue, return -1 if Queue is null
     * @return the size of this cell
     */
    @Override
    public int size() {
    	if (store==null) {
    		return -1;
    	} else { 
    		return store.size();
    	}
    }
   
    /**
     * Retain a number of Entry from the Queue starting from first to the number specified by quantity.
     * Which is: if quantity=10 then all entries from beginning (0) to index=quantity-1 are removed.
     * @return a sorted list containing all entry
     */
    @Override
    LinkedList<T> unload(int quantity) throws QueueException {
    	// no action taken if we are empty or quantity is less than 1...
    	if (this.isEmpty() || quantity <1) {
    		return null;
    	}
    	
    	// backup...
    	LinkedList<T> ll = null;
    	
        // each element in container is downloaded to the list
        // we acquire the monitor on store...
        synchronized(store) {
        	//this.setWorking(true);  // start operation...
        	ll = new LinkedList<T>(store.subList(0, quantity-1));
	        store.removeAll(ll);
	        //this.setWorking(false); // end operation...
	        store.notifyAll();
        }
        // returning...
        return ll;
    }
    
    /**
     * Remove all Entry from Queue
     * @param index
     * @return LinkedList of E
     */
    @Override
    LinkedList<T> unload() throws QueueException {
    	// we can stop our QueueDumper if we are empty...
    	if (this.isEmpty()) {
    		return null;
    	}
    	
    	// backup...
    	LinkedList<T> ll=null;
    	
        // each element in container is downloaded to the list
        // we acquire the monitor for this operation...
        synchronized(store) {
        	//this.setWorking(true);  // start operation...
        	ll = new LinkedList<T>(store);
	        store.removeAll(ll);
	        //this.setWorking(false); // end operation...
	        store.notifyAll();
        }
        
        // returning...
        return ll;   
    }    

	/**
	 * Getter for working
	 * @param working
	 */
	private final void setWorking(boolean working) {
		this.working = working;
	}

	/**
	 * Getter for working
	 * @return working
	 */
	public final boolean isWorking() {
		return working;
	}
	
	/**
	 * Kill this Queue...
	 */
	@Override
	public void kill() {
		if (store!=null) {
			store.clear();
		}
	}
	
}
